<!DOCTYPE html>
<meta charset="utf-8">
<title>GopherCon 2017 Contribution Dashboard</title>
<meta name=viewport content="width=device-width,minimum-scale=1,maximum-scale=1">
<style>
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
a:link,
a:visited {
  color: #fff;
}
.Site {
  background-color: #2a2a2a;
  color: #fff;
  display: flex;
  flex-direction: column;
  font: 14px 'Helvetica-Neue', Helvetica, sans-serif;
  height: 100vh;
  text-rendering: optimizeLegibility;
}
.Site-body {
  display: flex;
  flex: 1;
}
.Site-content {
  border-right: 1px solid #3a3939;
  overflow: hidden;
  padding: 1.5em;
  width: 60%;
}
.Site-activity {
  background-color: #323232;
  flex: 1;
  overflow: scroll;
}
.Site-logo {
  height: 60px;
  margin-right: 2em;
}
.Site-pointsTitle,
.Site-points {
  text-align: center;
}
.Site-pointsTitle {
  font-size: 4vh;
  letter-spacing: 1px;
  text-transform: uppercase;
}
.Site-points {
  font-size: 30vh;
}
.u-avatar {
  border-radius: 50%;
}
.AvatarGroup {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.AvatarGroup-avatar {
  height: 6vh;
  margin: 0 .5em .5em 0;
  width: 6vh;
}
.Activity {
  align-items: flex-start;
  background-color: #323232;
  border-bottom: 1px solid #2d2d2d;
  border-top: 1px solid #404040;
  display: flex;
  font-size: 2vw;
  padding: 1em;
}
.Activity-avatar {
  height: 2.5em;
  margin-right: .75em;
  width: 2.5em;
}
.Activity-main {
  flex: 1;
  margin-right: .15em;
}
.Activity-points {
  color: #85ebf9;
  font-size: .85em;
  margin-top: .25em;
}
.Activity-icon {
  font-size: 2em;
}
.Site-footer {
  align-items: center;
  background: #3a3737;
  display: flex;
  justify-content: space-between;
  padding: 1em 1.5em;
}
</style>
<body class="Site">
  <div class="Site-body">
    <main class="Site-content">
      <div class="Site-pointsTitle">Total points</div>
      <div class="Site-points js-totalPoints">0</div>
      <div class="AvatarGroup js-avatarGroup"></div>
    </main>
    <aside class="Site-activity js-activityList"></aside>
  </div>
  <footer class="Site-footer">
    <img class="Site-logo" src="https://github.com/ashleymcnamara/gophers/raw/master/GoCommunity.png" alt="Go Community">
    <div>
      Gopher artwork by <a href="https://github.com/ashleymcnamara/gophers" target="_blank">Ashley McNamara</a>
      based on the original artwork by the amazing
      <a href="https://reneefrench.blogspot.com/" target="_blank">Renee French</a>.
      Licensed under a <a href="http://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.</a>
    </div>
  </footer>
</body>

<template class="activityTemplate">
  <div class="Activity">
    <img class="u-avatar Activity-avatar js-avatar">
    <div class="Activity-main">
      <div class="Activity-body js-body"></div>
      <div class="Activity-points js-points"></div>
    </div>
    <div class="Activity-icon js-icon"></div>
  </div>
</template>

<script>
(function() {
  'use strict';

  const ActivityType = {
    AMEND_CHANGE: 'AMEND_CHANGE',
    CREATE_CHANGE: 'CREATE_CHANGE',
    MERGE_CHANGE: 'MERGE_CHANGE',
    REGISTER: 'REGISTER',
  };

  const iconMap = {};
  iconMap.AMEND_CHANGE = '👍';
  iconMap.CREATE_CHANGE = '👏';
  iconMap.MERGE_CHANGE = '🤘';
  iconMap.REGISTER = '🎉';

  let lastUpdateMs = 0;
  getActivities(lastUpdateMs);
  startUpdateLoop();

  function getActivities(since) {
    const url = `/_/activities?since=${since}`;
    fetch(url).then(resp => {
      resp.json().then(obj => {
        processAll(obj.activities);
        updatePoints(obj.totalPoints);
      });
    }).catch(err => {
      console.error(err);
    });
  }

  function startUpdateLoop() {
    window.setInterval(() => {
      getActivities(lastUpdateMs);
    }, 5000);
  }

  // Username => img element.
  let avatarMap = {};

  function processAll(activities) {
    const activityListEl = document.querySelector('.js-activityList');
    const avatarGroupEl = document.querySelector('.js-avatarGroup');
    activities.forEach(activity => {
      if (!avatarMap[activity.gitHubUser]) {
        const el = createAvatarEl(activity.gitHubUser);
        el.classList.add('AvatarGroup-avatar');
        avatarMap[activity.gitHubUser] = el;
      }
      avatarGroupEl.insertBefore(avatarMap[activity.gitHubUser],
          avatarGroupEl.firstChild);
      const activityEl = createActivityEl(activity);
      activityListEl.insertBefore(activityEl, activityListEl.firstChild);
    });
    if (activities.length > 0) {
      lastUpdateMs = new Date(activities[activities.length - 1].created).getTime();
    }
  }

  function updatePoints(points) {
    document.querySelector('.js-totalPoints').textContent = points;
  }

  function createAvatarEl(username) {
    let img = document.createElement('IMG');
    img.classList.add('u-avatar');
    img.src = avatarSrc(username);
    img.alt = avatarAlt(username);
    return img;
  }

  function avatarSrc(username) {
    return `https://github.com/${encodeURIComponent(username)}.png?size=100`;
  }

  function avatarAlt(username) {
    return `Avatar for ${username}`;
  }

  function activityBody(type, username) {
    switch (type) {
      case ActivityType.AMEND_CHANGE:
      return `${username} just amended a change!`;

      case ActivityType.CREATE_CHANGE:
      return `${username} just created a change!`;

      case ActivityType.MERGE_CHANGE:
      return `${username} just merged a change!`;

      case ActivityType.REGISTER:
      return `${username} just registered!`;
    }
  }

  function pointsStr(points) {
    return `+${points} Point` + (points > 1 ? 's' : '');
  }

  function createActivityEl(activity) {
    const tmpl = document.querySelector('.activityTemplate');
    const imgEl = tmpl.content.querySelector('.js-avatar');
    imgEl.src = avatarSrc(activity.gitHubUser);
    imgEl.alt = avatarAlt(activity.gitHubUser);
    tmpl.content.querySelector('.js-icon').textContent = iconMap[activity.type];
    tmpl.content.querySelector('.js-body').textContent =
        activityBody(activity.type, activity.gitHubUser);
    tmpl.content.querySelector('.js-points').textContent =
        pointsStr(activity.points);
    return document.importNode(tmpl.content, true);
  }
})();
</script>